﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DDD {

    /// <summary>
    /// アニメーション・トラック
    /// </summary>
    /// <remarks>
    /// アニメーショントラックは１つのプロパティの値の時間変化を表します。
    /// 通常複数のトラックをまとめてクリップとし、クリップ単位でターゲットオブジェクトにアニメーションをセットします。
    /// 元データとして<cref>KeyframeSequence</cref>を使用します。
    /// </remarks>
    public class AnimationTrack : Object {
        #region Field
        KeyframeSequence[] keyframeSequences;
        string targetProperty;
        #endregion

        #region Constructor

        /// <summary>
        /// <see cref="AnimationTrack"/>オブジェクトを作成するコンストラクター
        /// </summary>
        /// <param name="keyframeSequence">このトラックで使用するキーフレームシーケンス</param>
        /// <param name="targetProperty">このトラックが修飾するプロパティの名前</param>
        public AnimationTrack (KeyframeSequence keyframeSequence, string targetProperty) :
            this (new[] { keyframeSequence }, targetProperty) {
        }

        /// <summary>
        /// <see cref="AnimationTrack"/>オブジェクトを作成するコンストラクター
        /// </summary>
        /// <remarks>
        /// このトラックは配列型のプロパティの値を書き換えるため複数のキーフレームを渡します。
        /// 全てのキーフレームは「類似している」(<see cref="KeyframeSequence.Similar"/>=trueの)必要があります。
        /// </remarks>
        /// <param name="keyframeSequences">このトラックで使用するキーフレームシーケンスの配列</param>
        /// <param name="targetProperty">このトラックが修飾するプロパティの名前</param>
        public AnimationTrack (KeyframeSequence[] keyframeSequences, string targetProperty) {
            if (keyframeSequences == null || keyframeSequences.Length == 0) {
                throw new ArgumentNullException ("KeyframeSequence is null");
            }
            if (targetProperty == null || targetProperty == "") {
                throw new ArgumentNullException ("Target property is null or empty");
            }
            if (!keyframeSequences.All (x => x.Similar (keyframeSequences[0]))) {
                throw new ArgumentException ("All keyframes are not similar");
            }

            this.keyframeSequences = (KeyframeSequence[])keyframeSequences.Clone ();
            this.targetProperty = targetProperty;
        }
        #endregion

        #region Property

        /// <summary>
        /// このトラックで使用するキーフレームシーケンスを列挙する列挙子
        /// </summary>
        public IEnumerable<KeyframeSequence> KeyframeSequences {
            get { return keyframeSequences; }
        }

        /// <summary>
        /// このトラックに設定されたキーフレームシーケンスの個数
        /// </summary>
        public int KeyframeSequenceCount {
            get { return keyframeSequences.Length; }
        }

        /// <summary>
        /// このトラックが修飾するプロパティの名前
        /// </summary>
        public string TargetProperty {
            get { return targetProperty; }
        }

          /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (keyframeSequences);
                objs.RemoveAll (x=>x==null);
                return objs;
            }
        }

        #endregion

        #region Method
        /// <summary>
        /// このアニメーショントラックをサンプリングします
        /// </summary>
        /// <remarks>
        /// このアニメーショントラックをローカル時間を基準にサンプリングし、結果をfloatの配列で受け取ります。
        /// この関数はサンプリング結果を配列の引数で受け取るため配列型のターゲットプロパティでのみ使用可能です。
        /// 値はキーフレームの型にかかわらずfloatで受け取ります。
        /// </remarks>
        /// <param name="localTime">サンプリング時刻（ローカル時間）</param>
        /// <param name="value">サンプリング結果を書き込むfloatの配列</param>
        public void Sample (float localTime, float[] value) {
            Sample (localTime, 0, value);
        }

        /// <summary>
        /// このアニメーショントラックをサンプリングします
        /// </summary>
        /// <param name="localTime">サンプリング時刻（ローカル時間）</param>
        /// <param name="index">キーフレームシーケンス番号</param>
        /// <param name="value">結果を書き込むfloatの配列</param>
        public void Sample (float localTime, int index, float[] value) {
            if (index < 0 || index > KeyframeSequenceCount) {
                throw new IndexOutOfRangeException ("Index is invalid");
            }
            if (value == null) {
                throw new ArgumentNullException ("Value is null");
            }
            if (value.Length < keyframeSequences[index].ComponentCount) {
                throw new ArgumentException ("Value is too short");
            }

            keyframeSequences[index].Sample (localTime, value);
        }

        /// <summary>
        /// このトラックに設定されたキーフレームシーケンスを取得します
        /// </summary>
        /// <param name="index">キーフレームシーケンス番号</param>
        /// <returns></returns>
        public KeyframeSequence GetKeyframeSequence (int index) {
            if (index < 0 || index >= KeyframeSequenceCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return keyframeSequences[index];
        }


        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>文字列</returns>
        public override string ToString () {
            return string.Format ("AnimationTrack: Target={0}, KeyFrameSequences={1}", TargetProperty, KeyframeSequenceCount);
        }
        #endregion
    }

}
